package com.acme.HashMapInteger;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.carrotsearch.sizeof.*;

public class HashMapTest {

	private static final int EXECUTIONS = 100000;
	private static final int EXECUTIONS_BLOCK = 100;
	private static final int NUM_ELEMENTS = 1000000;

	public static void main(String[] args) {
		testMemWithThirdpartyFunc();
		testPerformance();
	}

	private static void testPerformance() {
		List<Long> stdMapGetTime, stdMapPutTime;
		List<Long> myMapWrapperGetTime, myMapWrapperPutTime;
		List<Long> myMapPrimitiveGetTime, myMapPrimitivePutTime;
		int[] randomKeys = generateRandomKeys(NUM_ELEMENTS);
		
		stdMapGetTime = testGenericMapGetPerformance(new HashMap<Integer, Integer>(),  randomKeys);
		stdMapPutTime = testGenericMapPutPerformance(new HashMap<Integer, Integer>(),  randomKeys);
		
		myMapWrapperGetTime = testGenericMapGetPerformance(new IntHashMap(),  randomKeys);
		myMapWrapperPutTime = testGenericMapPutPerformance(new IntHashMap(),  randomKeys);
		
		myMapPrimitiveGetTime = testMyMapGetPerformance(randomKeys);
		myMapPrimitivePutTime = testMyMapPutPerformance(randomKeys);
		
		System.out.println("Standard Integer HashMap average get times, ns: "+stdMapGetTime.toString());
		System.out.println("IntHashMap average get times through wrapping, ns: "+myMapWrapperGetTime.toString());
		System.out.println("IntHashMap average get times without wrapping, ns: "+myMapPrimitiveGetTime.toString());
		
		System.out.println("Standard Integer HashMap average put times, ns: "+stdMapPutTime.toString());
		System.out.println("IntHashMap average put times through wrapping, ns: "+myMapWrapperPutTime.toString());
		System.out.println("IntHashMap average put times without wrapping, ns: "+myMapPrimitivePutTime.toString());
	}

	private static List<Long> testGenericMapGetPerformance(Map<Integer, Integer> map, int[] randomKeys) {
		int currentMapSize;
		long executionTime = 0;
		long startTime = 0;
		List<Long> stdMapTime = new ArrayList<Long>();
		int rndKey;
		
		for (int powOfTen = 1; powOfTen <= 6; powOfTen++) {
			currentMapSize = (int) Math.pow(10, powOfTen);

			for (int i = 0; i < EXECUTIONS / EXECUTIONS_BLOCK; i++) {
				rndKey = (int) Math.floor(Math.random()	* (NUM_ELEMENTS - EXECUTIONS_BLOCK));
				map = generateGenericMap(map,currentMapSize, randomKeys);

				startTime = System.nanoTime();
				for (int j = 0; j < EXECUTIONS_BLOCK; j++) {
					map.get(randomKeys[rndKey+j]);
				}
				executionTime += System.nanoTime() - startTime;
			}

			System.out.println(executionTime / EXECUTIONS);
			stdMapTime.add(executionTime / EXECUTIONS);
		}
		return stdMapTime;
	}

	private static List<Long> testGenericMapPutPerformance(Map<Integer, Integer> map, int[] randomKeys) {
		int currentMapSize;
		long executionTime = 0;
		long startTime = 0;
		List<Long> stdMapTime = new ArrayList<Long>();
		int rndKey;
		
		for (int powOfTen = 1; powOfTen <= 6; powOfTen++) {
			currentMapSize = (int) Math.pow(10, powOfTen);

			for (int i = 0; i < EXECUTIONS / EXECUTIONS_BLOCK; i++) {
				rndKey = (int) Math.floor(Math.random()	* (NUM_ELEMENTS - EXECUTIONS_BLOCK));
				map = generateGenericMap(map,currentMapSize, randomKeys);

				startTime = System.nanoTime();
				for (int j = 0; j < EXECUTIONS_BLOCK; j++) {
					map.put(randomKeys[rndKey+j],1);
				}
				executionTime += System.nanoTime() - startTime;
			}

			System.out.println(executionTime / EXECUTIONS);
			stdMapTime.add(executionTime / EXECUTIONS);
		}
		return stdMapTime;
	}

	
	
	private static List<Long> testMyMapGetPerformance(int[] randomKeys) {
		int currentMapSize;
		long executionTime = 0;
		long startTime = 0;
		List<Long> stdMapTime = new ArrayList<Long>();
		
		for (int powOfTen = 1; powOfTen <= 6; powOfTen++) {
			currentMapSize = (int) Math.pow(10, powOfTen);
			//IntHashMap stdMap = generateIntHashMap(currentMapSize, randomKeys);
			IntHashMap stdMap;

			for (int i = 0, rndKey = (int) Math.floor(Math.random()	* EXECUTIONS); 
					i < EXECUTIONS / EXECUTIONS_BLOCK; i++, rndKey += i) {
				stdMap = (IntHashMap) generateGenericMap(new IntHashMap(currentMapSize), currentMapSize, randomKeys);

				startTime = System.nanoTime();
				for (int j = 0; j < EXECUTIONS_BLOCK; j++) {
					stdMap.get(randomKeys[rndKey+j]);
				}
				executionTime += System.nanoTime() - startTime;
			}

			System.out.println(executionTime / EXECUTIONS);
			stdMapTime.add(executionTime / EXECUTIONS);
		}
		return stdMapTime;
	}

	private static List<Long> testMyMapPutPerformance(int[] randomKeys) {
		int currentMapSize;
		long executionTime = 0;
		long startTime = 0;
		List<Long> stdMapTime = new ArrayList<Long>();
		int rndKey;
		
		for (int powOfTen = 1; powOfTen <= 6; powOfTen++) {
			currentMapSize = (int) Math.pow(10, powOfTen);
			IntHashMap map;
			for (int i = 0; i < EXECUTIONS / EXECUTIONS_BLOCK; i++) {
				rndKey = (int) Math.floor(Math.random()	* (NUM_ELEMENTS - EXECUTIONS_BLOCK));
				map = (IntHashMap) generateGenericMap(new IntHashMap(currentMapSize),currentMapSize, randomKeys);

				startTime = System.nanoTime();
				for (int j = 0; j < EXECUTIONS_BLOCK; j++) {
					map.put(randomKeys[rndKey+j],1);
				}
				executionTime += System.nanoTime() - startTime;
			}

			System.out.println(executionTime / EXECUTIONS);
			stdMapTime.add(executionTime / EXECUTIONS);
		}
		return stdMapTime;
	}

	
	private static Map<Integer, Integer> generateGenericMap(Map<Integer, Integer> map, int sizeEachMap, int[] randomKeys) {
		map.clear();
		for (int i = 0; i < sizeEachMap; i++) {
			map.put(randomKeys[i], randomKeys[i]);
		}
		return map;
	}
	
	private static void testMemWithThirdpartyFunc() {
		List<Float> stdMapMem = new ArrayList<Float>();
		List<Float> myMapMem = new ArrayList<Float>();

		int[] randomKeys = generateRandomKeys(NUM_ELEMENTS);
//		int[] randomKeys = generateConsequentKeys(NUM_ELEMENTS);
		int currentMapSize;

		for (int powOfTen = 1; powOfTen <= 6; powOfTen++) {
			currentMapSize = (int) Math.pow(10, powOfTen);
			Map<Integer, Integer> standardMap = new HashMap<Integer, Integer>();
			IntHashMap myMap = new IntHashMap(currentMapSize);
			int randNum;

			for (int i = 0; i < currentMapSize; i++) {

				standardMap.put(randomKeys[i], i);
				myMap.put(randomKeys[i], i);
			}

			stdMapMem.add((float) RamUsageEstimator.sizeOf(standardMap)
					/ currentMapSize);
			myMapMem.add((float) RamUsageEstimator.sizeOf(myMap)
					/ currentMapSize);
		}
		System.out.println(stdMapMem);
		System.out.println(myMapMem);
	}

	private static int[] generateRandomKeys(int numElements) {
		int[] arr = new int[numElements];
		for (int i = 0; i < numElements; i++) {
			arr[i] = (int) Math.round(Math.random() * Integer.MAX_VALUE);
		}
		return arr;
	}

	private static int[] generateConsequentKeys(int numElements) {
		int[] arr = new int[numElements];
		for (int i = 0; i < numElements; i++) {
			arr[i] = i + 1;
		}
		return arr;
	}
}


